package dom.xpath.query.xpath.expr;

import dom.xpath.data.Data;
import dom.xpath.query.EvalException;
import dom.xpath.query.xpath.XPContext;
import dom.xpath.query.xpath.XPOptimizer;
import dom.xpath.query.xpath.internal.FTIndex;
import dom.xpath.query.xpath.locpath.LocationPath;
import dom.xpath.query.xpath.locpath.RelativeLocationPath;
import dom.xpath.query.xpath.locpath.Step;
import dom.xpath.query.xpath.locpath.TestNode;
import dom.xpath.query.xpath.values.Bool;
import dom.xpath.query.xpath.values.Comp;
import dom.xpath.query.xpath.values.Literal;
import dom.xpath.query.xpath.values.NodeSet;
import dom.xpath.util.Array;
import dom.xpath.util.Token;
import static dom.xpath.query.xpath.XPText.*;

/**
 * FTContains Expression; used for fulltext operations.
 * 
 * @author Workgroup DBIS, University of Konstanz 2005-07, ISC License
 * @author Christian Gruen
 */
public final class FTContains extends Comparison {
  /** FullText options. */
  FTOption option;
  /** Result of index request - currently only pre values are saved. */
  int[] ids;
 
  /**
   * Constructor.
   * @param e1 first expression
   * @param e2 second expression to compare with first
   * @param opt fulltext options
   */
  public FTContains(final Expression e1, final Expression e2, final FTOption opt) {
    expr1 = e1;
    expr2 = e2;
    option = opt;
    type = Comp.FTCONTAINS;
    
  }

  @Override
  public Expression compile(final XPContext ctx) throws EvalException {
    
    ctx.ftoption = option;
    if (expr2 instanceof FTOr) {
     
    }
    
    expr1 = expr1.compile(ctx);
    expr2 = expr2.compile(ctx);

    XPOptimizer.addText(expr1, ctx);
    XPOptimizer.addText(expr2, ctx);

   return this;
  }

  @Override
  public Bool eval(final XPContext ctx) throws EvalException {
    ctx.ftoption = option;
    
    if(expr1 instanceof LocationPath && expr2 instanceof FTUnaryNot) {
      FTContains f = (FTContains) ((FTUnaryNot) expr2).exprs[0];
      // evaluate location path to get resulting nodes
      ctx.local = (NodeSet) ctx.eval(expr1);

      final byte[] l = ((Literal) f.expr2).str();
      // check if index is fulltext index exists
      final Data data = ctx.local.data;
      if(data.meta.ftxindex) {
        if(ctx.local.ftids == null) {
          ctx.local.ftids = Array.extractIDsFromData(data.ftIDs(l, f.option));
        }
        return Bool.get(expr2.eval(ctx).bool());
      }
    }
    
    // use conventional check for other cases..
    return super.eval(ctx);
  }

  @Override
  public Expression indexEquivalent(final XPContext ctx, final Step curr)
    throws EvalException {
    
    // simple ftcontains invert path for index access
    if (expr1 instanceof LocationPath && expr2 instanceof Literal) {
      final LocationPath path = (LocationPath) expr1;
      final byte[] lit = ((Literal)  expr2).str();

      ctx.compInfo(OPTFTINDEX);
      
      if (Token.indexOf(lit, ' ') > 0) {
        return new Path(new FTIndex(lit, option, true, false), 
            path.invertPath(curr));
      }
        
      return new Path(new FTIndex(lit, option, true, true), 
          path.invertPath(curr));
      
    } else if ((expr1 instanceof LocationPath) && 
        (expr2 instanceof FTOr || expr2 instanceof FTAnd 
            || expr2 instanceof FTMildNot)) {
      // find index equivalents
      Expression indexExprs = expr2.indexEquivalent(ctx, curr);

      if(indexExprs == null) 
        return null;
   
      final LocationPath path = (LocationPath) expr1;
      ctx.compInfo(OPTOR4);
      
      return new Path(indexExprs, path.invertPath(curr));
      //return indexExprs;
    } else if ((expr1 instanceof Literal || expr1 instanceof FTOr ||
        expr1 instanceof FTAnd || expr1 instanceof FTMildNot) 
        && expr2 instanceof Literal) {
      final byte[] lit = ((Literal)  expr2).str();
      
      ctx.compInfo(OPTFTINDEX);

      if (Token.indexOf(lit, ' ') > 0) {
        return new FTIndex(lit, option, false, false);
      }
      return new FTIndex(lit, option, false, true);
    } else if (expr1 instanceof Literal && expr2 instanceof FTUnaryNot) {
      final byte[] lit = ((Literal)  expr2).str();

      ctx.compInfo(OPTFTINDEX);

      if (Token.indexOf(lit, ' ') > 0) {
        return new FTIndex(lit, option, false, false);
      }
      return new FTIndex(lit, option, false, true);
    }

    return null;
  } 

  @Override
  public int indexSizes(final XPContext ctx, final Step curr,
      final int min) {
    
    // check which expression is a location path
    if(!(ctx.local.data.meta.ftxindex && expr1 instanceof RelativeLocationPath
        )) return Integer.MAX_VALUE;
    // (expr2 instanceof Literal)
    
    // check if index can be applied
    final LocationPath path = (RelativeLocationPath) expr1;
    final Step step = path.steps.last();
    final boolean text = step.test == TestNode.TEXT && step.preds.size() == 0;
    if(!text || !path.checkAxes()) return Integer.MAX_VALUE;

    if (expr1 instanceof FTUnaryNot || expr2 instanceof FTUnaryNot) 
      return Integer.MAX_VALUE; 
    
    // high selectivity - always create index access
    return 1;
    
  }
}
